package com.utilities.IO.fileWriters;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.TreeSet;

import com.utilities.UtilMethods;
import com.utilities.dataStructures.Range;

import static com.utilities.IO.fileWriters.Gff3FileConstants.*;

/**
 * This class defines the logic to convert a scaffold into a gff3 file. 
 * @author Parit Bansal
 *
 */
public class PrepareGFF3 
{
	private String inputFile = null;
	private String inputFile_gbk = null;
	private String outpuFile = null;
	private String outputConfigFile = null;
	private String seqName = null;
	private boolean addSeqInfo = false; 

	public PrepareGFF3 setAddSeqInfo(boolean addSeqInfo) {
		this.addSeqInfo = addSeqInfo;
		return this;
	}

	public PrepareGFF3 makeGff3File() throws IOException
	{

		// a list to keep track of what genes have already been encountered
		// so that a unique Id be assigned to them
		ArrayList<String> list = new ArrayList<String>();

		// varibale to keep track of the largest position encountered 
		// and this will be equal to the length of the sequence
		int largestIndex = 0;

		// variable to store the database name mentioned in the comments.
		String database = null;

		if (!UtilMethods.isNullOrEmpty(outputConfigFile))
		{
			File fl = new File(outpuFile+"/"+seqName);
			if(!(fl.exists() && fl.isDirectory()))
			{
				fl.mkdir();
			}
			outpuFile = outpuFile + "/" + seqName + "/" + seqName + ".gff";
			outputConfigFile = outputConfigFile + "/" + seqName + ".conf";
			prepareConfigFile();
		}

		File fl = new File(inputFile);
		BufferedReader buff = new BufferedReader(new FileReader(fl));

		// create a handle on a temp file
		FileWriter fw = new FileWriter(outpuFile+"_temp");

		// read one line at a time from the input file;
		String readLine = buff.readLine();
		int index = 0;
		// keep reading the file till a null character is read
		while(readLine!=null)
		{
			// an uncommented line is read.
			if (readLine.charAt(0)!='#')
			{
				// database name must have been read by now
				// as comments are at the top of the file
				assert(database != null);

				// split the readline into different columns.
				String[] params = readLine.split("\\t");

				StringBuffer strbuff = new StringBuffer();

				// params[0] is the name of the gene
				// chk if we have already read this gene or not.
				if (!list.contains(params[0]))
				{
					// if not then add it to the list.
					list.add(params[0]);
				}

				// fetch the index of the gene in the list.
				index = list.indexOf(params[0]);

				// create the attribute string as follows:
				// PARENT=PARENT ID=MATCH<indexOfGene> NAME=<nameOfTheGene> eVal=<e-value of he match>
				// mM=<number of mismaches in the match> gaps=<number of gaps encountered in the match>
				String attribute = String.format("Parent=%s;%s=MATCH%s;%s=%s;%s=%s;%s=%s;%s=%s;%s=%s;%s=%s",
						seqName,ID,index,NAME,params[0],eVal,params[10],mM,params[4],
						gaps,params[5],gStart,params[6],gEnd,params[7]);
				// create a row to be written in the file
				strbuff.append(String.format("%s\t%s\t%s\t%s\t%s\t%s\t.\t.\t%s\n",seqName,database,
						feature_gene,params[8].trim(),params[9].trim(),params[2],attribute));
				fw.append(strbuff.toString());

				int startIndex = Integer.valueOf(params[8]);
				int endIndex = Integer.valueOf(params[9]);

				// find the largest index encountered thus far
				// we are checking for both the startIndex and endIndex
				// coz in some cases startIndex could be bigger when a reverse strand is reported.
				if (startIndex > largestIndex) largestIndex = startIndex;
				if (endIndex > largestIndex) largestIndex = endIndex;
			}
			// if this is a comment line containing database name
			else if (readLine.contains("Database:"))
			{
				database = readLine.split(database_identifier)[1].trim();
				database = database.substring(database.lastIndexOf("/"));
			}
			// read the next line
			readLine = buff.readLine();
		}
		fw.close();

		// create the real output file and write the parent row first
		fw = new FileWriter(outpuFile);
		String attribute = String.format("%s=%s;%s=%s",ID,seqName,NAME,seqName);
		fw.append(String.format("## sequence-region %s %s %s\n%s\t%s\t%s\t%s\t%s\t%s\t.\t.\t%s\n",
				seqName,1,largestIndex,seqName,
				database,parent_feature,
				1,largestIndex,".",attribute));
		/*
		 * Copy rest of the content from temp file to this file.
		 */
		RandomAccessFile file = new RandomAccessFile(outpuFile+tempFileSuffix, "r");
		readLine = file.readLine();
		while(readLine != null)
		{
			fw.append(readLine+"\n");
			readLine = file.readLine();
		}
		file.close();
		System.out.println("writing invalidators");
		this.writeInvalidators(fw,index,database);

		// adding the sequence info
//		System.out.println("cheking if GBK file is provided");
		if (inputFile_gbk != null && addSeqInfo)
		{
			System.out.println("adding sequence info");
			fetchSequenceFromGbkFile(fw, inputFile_gbk);
			fw.close();
		}
		else
		{
			fw.close();
			System.out.println("skipping sequence info");
		}

		// delete the temp file
		fl = new File(outpuFile+tempFileSuffix);
		fl.delete();
		return this;
	}

	public void fetchSequenceFromGbkFile(FileWriter fw, String inputgbk)
	throws IOException, FileNotFoundException {
		String readLine = null;
		RandomAccessFile file = new RandomAccessFile(inputgbk, "r");
		fw.append(String.format(">%s\n", seqName));
		readLine = file.readLine();
		while(!readLine.startsWith("ORIGIN"))
		{
			readLine = file.readLine();
		}
		readLine = file.readLine();
		while(readLine != null)
		{
			String params[] = readLine.trim().split(" ");
			for (int i = 1 ; i < params.length; i++)
			{
				fw.append(params[i]);
			}
			fw.append("\n");

			readLine = file.readLine();
		}
		file.close();
	}

	public void fetchSequenceFromGbkFile(String outFile, String inputgbk)
	throws IOException, FileNotFoundException {
		FileWriter fw = new FileWriter(outFile);
		fetchSequenceFromGbkFile(fw, inputgbk);
		fw.close(); 
	}

	private void writeInvalidators(FileWriter fw, int index, String database) throws IOException
	{
		int startIndex = 0;
		int charsRead = 0;
		int currentLegth = 0;
		boolean flag = false;
		RandomAccessFile rand = new RandomAccessFile(inputFile_gbk, "r");
		String readline = rand.readLine();
		while(!readline.startsWith("ORIGIN"))
		{
			readline = rand.readLine();
		}
		readline = rand.readLine();
		//		if (readline.startsWith(">")) readline = rand.readLine();
		while(readline != null)
		{
			readline = readline.toUpperCase().trim();
			char[] chars = readline.toCharArray();
			for(char ch : chars)
			{
				if (ch == '1' || ch == '2' || 
						ch == '3' || ch == '4' ||
						ch == '5' || ch == '6' ||
						ch == '7' || ch == '8' ||
						ch == '9' || ch == ' ')
				{

				}
				else if (ch == 'N')
				{
					if (flag)
					{
						currentLegth++;
						charsRead++;
					}
					else
					{
						startIndex = charsRead + 1;
						currentLegth++;
						charsRead++;
						flag = true;
					}
				}
				else
				{
					if (flag)
					{
						String attribute = String.format("Parent=%s;%s=MATCH%s",
								seqName,ID,index);
						fw.append(String.format("%s\t%s\t%s\t%s\t%s\t.\t.\t.\t%s\n",seqName,database,
								feature_invalid,String.valueOf(startIndex),String.valueOf(startIndex+currentLegth-1),attribute));
						startIndex = 0;
						currentLegth = 0;
						charsRead++;
						index++;
						flag = false;
					}
					else
					{
						charsRead++;
					}
				}
			}
			readline = rand.readLine();
		}
		if (startIndex != 0)
		{
			String attribute = String.format("Parent=%s;ID=MATCH%s",
					seqName,index);
			fw.append(String.format("%s\t%s\t%s\t%s\t%s\t.\t.\t.\t%s\n",seqName,database,
					feature_invalid,String.valueOf(startIndex),String.valueOf(startIndex+currentLegth-1),attribute));
			index++;
		}
	}
	public void cleanGFF3File() throws IOException 
	{
		TreeSet<String> correctOnes = new TreeSet<String>();
		System.out.println("cleaning gff3 file " + outpuFile);
		FileWriter fw = new FileWriter(outpuFile + cleanSuffix);
		FileWriter fw1 = new FileWriter(outpuFile + othersSuffix);
		RandomAccessFile rand = new RandomAccessFile(outpuFile, "r");
		String readLine = rand.readLine();
		boolean copyAll = false;
		while(readLine != null)
		{
			if (readLine.startsWith("#") || copyAll)
			{
				fw.append(readLine+"\n");
				fw1.append(readLine+"\n");
			}
			else if (readLine.startsWith(">"))
			{
				if (addSeqInfo)
				{
					copyAll = true;
					fw.append(readLine+"\n");
					fw1.append(readLine+"\n");
				}
				else
					break;
			}
			else
			{
				String[] params = readLine.split("\\t");
				if (params[2].trim().equalsIgnoreCase(feature_gene))
				{
					String[] attributes = params[8].trim().split(";");
					int geneStartVal = 0;
					@SuppressWarnings("unused")
					int geneEndVal = 0;
					int missMatch = 0;
					int gps = 0;
					String name = null;
					double score = Double.parseDouble(params[5].trim());
					for (String att : attributes)
					{
						String[] NameValPair = att.split("=");
						if (NameValPair[0].equalsIgnoreCase(gStart))
						{
							geneStartVal = Integer.parseInt(NameValPair[1].trim()); 
						}
						else if (NameValPair[0].equalsIgnoreCase(gEnd))
						{
							geneEndVal = Integer.parseInt(NameValPair[1].trim());
						}
						else if (NameValPair[0].equalsIgnoreCase(mM))
						{
							missMatch = Integer.parseInt(NameValPair[1].trim());
						}
						else if (NameValPair[0].equalsIgnoreCase(gaps))
						{
							gps = Integer.parseInt(NameValPair[1].trim());
						}
						else if (NameValPair[0].equalsIgnoreCase(NAME))
						{
							name = NameValPair[1].trim();
						}
					}

					if (score == 100.00 && missMatch == 0 && gps == 0 && geneStartVal == 1)
					{
						fw.append(readLine+"\n");
						if (!correctOnes.contains(name))
						{
							correctOnes.add(name);
						}
					}
					else
					{
						if (!correctOnes.contains(name))
						{
							fw1.append(readLine+"\n");
						}
					}
				}
				else if (params[2].trim().equalsIgnoreCase(parent_feature))
				{
					fw.append(readLine+"\n");
					fw1.append(readLine+"\n");
				}
			}
			readLine = rand.readLine();
		}
		fw.flush();
		fw1.flush();
		fw.close();
		fw1.close();
	}

	public ArrayList<Range> findRanges() throws IOException
	{
		TreeSet<String> genesAlreadyCovered = new TreeSet<String>();
		ArrayList<Range> list = new ArrayList<Range>();
		BufferedReader bufferedReader = new BufferedReader(new FileReader(outpuFile + cleanSuffix));
		String readLine = bufferedReader.readLine();
		@SuppressWarnings("unused")
		int seqLength = 0;
		while(readLine != null)
		{
			// leave the commented line
			if (readLine.startsWith("#"))
			{

			}
			// get out as soon as sequence info starts
			else if (readLine.startsWith(">"))
			{
				break;
			}
			else
			{
				String[] params = readLine.split("\\t");
				if (params[2].equalsIgnoreCase(parent_feature))
				{
					seqLength = Integer.parseInt(params[4].trim());
				}
				else if (params[2].equalsIgnoreCase(feature_gene))
				{
					int start = Integer.parseInt(params[3]);
					int end = Integer.parseInt(params[4]);
					if (start > end)
					{
						int temp = start; start = end ; end = temp;
					}
					String geneName = null;
					String[] attributes = params[8].trim().split(";");
					for (String att : attributes)
					{
						String[] NameValPair = att.split("=");
						if (NameValPair[0].equalsIgnoreCase(NAME))
						{
							geneName = NameValPair[1].trim();
							break;
						}
					}
					if (!genesAlreadyCovered.contains(geneName))
					{
						Range range = new Range(start, end, geneName);
						list.add(range);
						genesAlreadyCovered.add(geneName);
					}

				}
			}
			readLine = bufferedReader.readLine();
		}
		bufferedReader.close();
		list.add(new Range(1,seqLength,"contig"));
		return list;
	}

	private void prepareConfigFile() throws IOException 
	{
		if (!new File(outputConfigFile).exists())
		{
			FileWriter fw = new FileWriter(outputConfigFile);
			fw.close();
		}
	}

	public PrepareGFF3 setInputFile(String inputFile) {
		this.inputFile = inputFile;
		return this;
	}

	public PrepareGFF3 setInputFile_gbk(String inputFileGbk) {
		this.inputFile_gbk = inputFileGbk;
		return this;
	}

	public PrepareGFF3 setOutputFile(String outpuFile) {
		this.outpuFile = outpuFile;
		return this;
	}

	public PrepareGFF3 setSeqName(String seqName) {
		this.seqName = seqName;
		return this;
	}

	public PrepareGFF3 setOutputConfigFile(String outputConfigFile) {
		this.outputConfigFile = outputConfigFile;
		return this;
	}
}// PrepareGFF3
